import 'dart:math';

import 'package:flutter/material.dart';

import '../common/coordinate.dart';

class Paper extends StatelessWidget {
  const Paper({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: CustomPaint(
        painter: PaperPainter(),
      ),
    );
  }
}

class PaperPainter extends CustomPainter {
  final Paint mPaint = Paint()
    ..style = PaintingStyle.fill
    ..color = Colors.deepPurpleAccent;
  final Path mPath = Path();
  final CoordinateByPath coordinate = CoordinateByPath();

  @override
  void paint(Canvas canvas, Size size) {
    coordinate.paint(canvas, size);
    // 原点坐标移动到屏幕中心
    canvas.translate(size.width / 2, size.height / 2);

    //testMoveToAndLineTo(canvas);
    //testRelativeMoveToAndLineTo(canvas);
    //testArcTo(canvas);
    //testActToPoint(canvas);
    //testConicTo(canvas);
    //testQuadraticBezierTo(canvas);
    //testCubicTo(canvas);
    //testAddRect(canvas);
    //testAddOval(canvas);
    testAddPolygon(canvas);
  }

  /// 测试 moveTo 和 lineTo 画线
  // moveTo相当于提起笔落到纸上的位置坐标，且坐标以画布原点为参考系。
  // lineTo相当于从落笔点画直线到期望的坐标点，且坐标以画布原点为参考系。
  void testMoveToAndLineTo(Canvas canvas) {
    mPath.reset();
    mPath
      ..moveTo(0, 0) // 移至(0,0)点
      ..lineTo(60, 80) // 从(0,0)画线到(60, 80) 点
      ..lineTo(60, 0) // 从(60,80)画线到(60, 0) 点
      ..lineTo(0, -80) // 从(60, 0)画线到(0, -80)点
      ..close(); // 闭合路径
    canvas.drawPath(mPath, mPaint);

    mPaint
      ..style = PaintingStyle.stroke
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2;
    mPath
      ..reset()
      ..moveTo(0, 0)
      ..lineTo(-60, 80)
      ..lineTo(-60, 0)
      ..lineTo(0, -80);
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 relativeMoveTo和relativeLineTo: 相对画线
  // 如果点位已经知道,使用 moveTo 和 lineTo 会比较方便，但很多情况下是不能直接知道的。
  // 比如在某点的基础上,画一条线，要求左移 10，上移 60，这样点位很难直接确定。
  // 使用 relative 系列方法就会非常简单。
  void testRelativeMoveToAndLineTo(Canvas canvas) {
    mPath.reset();
    mPaint
      ..style = PaintingStyle.fill
      ..color = Colors.green;

    mPath
      ..relativeMoveTo(0, 0)
      ..relativeLineTo(100, 120)
      ..relativeLineTo(-10, -60)
      ..relativeLineTo(60, -10)
      ..close();
    canvas.drawPath(mPath, mPaint);

    mPath.reset();
    mPaint
      ..style = PaintingStyle.stroke
      ..strokeJoin = StrokeJoin.round
      ..strokeWidth = 2;
    mPath
      ..relativeMoveTo(-200, 0)
      ..relativeLineTo(100, 120)
      ..relativeLineTo(-10, -60)
      ..relativeLineTo(60, -10)
      ..close();
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 arcTo: 圆弧
  // arcTo 用于圆弧路径，指定一个矩形域，形成椭圆。
  // 指定起始弧度，和扫描弧度，就可以从椭圆上截取出圆弧。
  // 最后一参代表是否强行移动，如果为true，如图左，绘制圆弧时会先移动到起点。
  void testArcTo(Canvas canvas) {
    resetPathPaint();

    // 绘制左侧
    var rect =
        Rect.fromCenter(center: const Offset(0, 0), width: 160, height: 100);
    mPath
      ..lineTo(30, 30)
      ..arcTo(rect, 0, pi * 1.5, true);
    canvas.drawPath(mPath, mPaint);

    mPath.reset();
    canvas.translate(200, 0);
    // 绘制右侧
    mPath
      ..lineTo(30, 30)
      ..arcTo(rect, 0, pi * 1.5, false);
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 arcToPoint和relativeArcToPoint: 点定弧
  // 当想要画圆弧到某个点，用 arcToPoint 会非常方便
  // 接受一个点位入参 Offset，是圆弧的终点，可指定圆弧半径radius、是否使用优弧、是否顺时针
  // 左侧: 使用优弧: largeArc: true ,逆时针:clockwise: false
  // 中间: 使用劣弧: largeArc: false,顺时针:clockwise: true
  // 右侧: 使用优弧: largeArc: true ,顺时针:clockwise: true
  // relativeArcToPoint方法即使用相对位置来加入圆弧路径，参数含义与上面一致。
  void testActToPoint(Canvas canvas) {
    resetPathPaint();

    // 中间
    mPath
      ..lineTo(80, -40)
      ..arcToPoint(const Offset(40, 40),
          radius: const Radius.circular(60), largeArc: false)
      ..close();
    canvas.drawPath(mPath, mPaint);

    // 左侧
    mPath.reset();
    canvas.translate(-150, 0);
    mPath
      ..lineTo(80, -40)
      ..arcToPoint(const Offset(40, 40),
          radius: const Radius.circular(60), largeArc: true, clockwise: false)
      ..close();
    canvas.drawPath(mPath, mPaint);

    // 右侧
    mPath.reset();
    canvas.translate(300, 0);
    mPath
      ..lineTo(80, -40)
      ..arcToPoint(const Offset(40, 40),
          radius: const Radius.circular(60), largeArc: true)
      ..close();
    canvas.drawPath(mPath, mPaint);

    mPath.reset();
    canvas.translate(-450, 0);
    mPath
      ..lineTo(80, -40)
      ..arcToPoint(const Offset(40, 40),
          radius: const Radius.circular(60), largeArc: false, clockwise: false)
      ..close();
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 conicTo 和 relativeConicTo: 圆锥曲线
  // conicTo 接收五个参数用于绘制圆锥曲线，包括椭圆线、抛物线和双曲线
  // 其中前两参是控制点，三四参是结束点，第五参是权重。(下图已画出辅助点)
  // 当权重< 1 时，圆锥曲线是椭圆线,如下左图
  // 当权重= 1 时，圆锥曲线是抛物线,如下中图
  // 当权重> 1 时，圆锥曲线是双曲线,如下右图
  // relativeConicTo方法即使用相对位置来加入圆锥曲线路径，参数含义与上面一致。
  void testConicTo(Canvas canvas) {
    const Offset p1 = Offset(80, -100);
    const Offset p2 = Offset(160, 0);

    resetPathPaint();

    // 抛物线
    mPath.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 1);
    canvas.drawPath(mPath, mPaint);

    // 椭圆线
    mPath.reset();
    canvas.translate(-180, 0);
    mPath.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 0.5);
    canvas.drawPath(mPath, mPaint);

    // 双曲线
    mPath.reset();
    canvas.translate(360, 0);
    mPath.conicTo(p1.dx, p1.dy, p2.dx, p2.dy, 1.5);
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 quadraticBezierTo 和 relativeQuadraticBezierTo: 二阶贝塞尔
  // quadraticBezierTo接收四个参数用于绘制二阶贝塞尔曲线。
  // 其中前两参是控制点,三四参是结束点。(下图已画出蓝色辅助点线)
  // relativeQuadraticBezierTo是在使用相对位置来加入二阶贝塞尔曲线路径。
  void testQuadraticBezierTo(Canvas canvas) {
    const Offset p1 = Offset(100, -100);
    const Offset p2 = Offset(160, 50);

    resetPathPaint();

    mPath.quadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
    mPath.relativeQuadraticBezierTo(p1.dx, p1.dy, p2.dx, p2.dy);
    canvas.drawPath(mPath, mPaint);
  }

  /// 测试 cubicTo 和 relativeCubicTo: 三阶贝塞尔
  // cubicTo接收六个参数用于绘制三阶贝塞尔曲线
  // 其中前两参是控制点1,三四参是控制点2,五六参是结束点。(下图已画出蓝色辅助点线)
  // relativeCubicTo是在使用相对位置来加入三阶贝塞尔曲线路径。
  void testCubicTo(Canvas canvas) {
    const Offset p1 = Offset(50, -100);
    const Offset p2 = Offset(100, 50);
    const Offset p3 = Offset(150, 100);

    mPath.cubicTo(p1.dx, p1.dy, p2.dx, p2.dy, p3.dx, p3.dy);
    mPath.relativeCubicTo(p1.dx, p1.dy, p2.dx, p2.dy, p3.dx, p3.dy);
    canvas.drawPath(mPath, mPaint);
  }

  /// addRect 和 addRRect: 添加类矩形
  // addRect用于在已有路径上添加矩形路径，接受一个Rect对象。
  // addRRect用于在已有路径上添加圆角矩形路径，接受一个RRect对象。
  void testAddRect(Canvas canvas) {
    const Offset p1 = Offset(100, 100);
    const Offset p2 = Offset(160, 160);
    resetPathPaint();
    Rect rect = Rect.fromPoints(p1, p2);
    mPath
      ..lineTo(100, 100)
      ..addRect(rect)
      ..relativeLineTo(100, -100)
      ..addRRect(RRect.fromRectXY(rect.translate(100, -100), 10, 10));
    canvas.drawPath(mPath, mPaint);
  }

  // addOval和addArc: 添加类圆形
  // addOval用于在已有路径上添加椭圆路径，接受一个Rect 对象。
  // addArc用于在已有路径上添加圆弧路径，接受一个Rect 对象，起始弧度、扫描弧度
  void testAddOval(Canvas canvas) {
    const Offset p1 = Offset(100, 100);
    const Offset p2 = Offset(160, 160);
    resetPathPaint();
    Rect rect = Rect.fromPoints(p1, p2);
    mPath
      ..lineTo(100, 100)
      ..addOval(rect)
      ..relativeLineTo(100, -100)
      ..addArc(rect.translate(160, -100), 0, pi);
    canvas.drawPath(mPath, mPaint);
  }

  // addPolygon: 添加多边形路径 、addPath：添加路径
  // addPolygon用于在已有路径上添加多边形路径，接受一个List<Offset>对象。
  // addPath用于在已有路径上添加路径，接受一个Path对象和偏移量Offset。
  void testAddPolygon(Canvas canvas) {
    const Offset p0 = Offset(100, 100);
    resetPathPaint();

    mPath
      ..lineTo(100, 100)
      ..addPolygon([
        p0,
        p0.translate(20, -20),
        p0.translate(40, -20),
        p0.translate(60, 0),
        p0.translate(60, 20),
        p0.translate(40, 40),
        p0.translate(20, 40),
        p0.translate(0, 20),
      ], true)
      ..addPath(
          Path()..relativeQuadraticBezierTo(125, -100, 260, 0), Offset.zero)
      ..lineTo(160, 100);
    canvas.drawPath(mPath, mPaint);
  }

  void resetPathPaint() {
    mPath.reset();
    mPaint
      ..color = Colors.purpleAccent
      ..strokeWidth = 2
      ..style = PaintingStyle.stroke;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
